/* eslint-disable no-await-in-loop */
/* eslint-disable no-param-reassign */
/* eslint-disable require-atomic-updates */
/* eslint-disable prefer-const */
/* eslint-disable @iceworks/best-practices/recommend-polyfill */
/* eslint-disable no-loop-func */
/* eslint-disable no-constant-condition */
import { makeAutoObservable, runInAction, toJS } from 'mobx';
import * as turf from '@turf/turf';
import * as _api from './api';
import { t } from '@lingui/macro';

const { BMapGL } = global;
const geoParser = new BMapGL.GeoJSONParse({ reference: 'WGS84' });
// const convertor = new BMapGL.Convertor();

const PG_SIZE = 10;
export const LINE_PROP = {
  reference: 'WGS84',
  // dataSource: v.line,
  polylineStyle: {
    strokeColor: '#0000FF',
    strokeWeight: 3,
  },
};

export default class TaskMapStore {
  pointsSubscription = null;
  _points = []; // 车辆位置
  pointKind = 1; // 1 - 服务中; 2 - 空闲; 0 - 全部s
  selectedPoint = null;
  pointKeyword = '';
  _trace = null; // 选中的轨迹
  traceKind = 0; // 0 - 进行中; 1 - 历史轨迹
  traces = [];
  traceTotal = 0;

  mapContext = { // 跨组件的动画上下文
    mapRef: undefined,
    carRef: undefined,
    carLabelRef: undefined,
    path: [], // 轨迹坐标
    pathIdx: 0, // 当前动画的car坐标索引
    ratio: 50, // 回放速度
    animating: false,
    paused: false,
    reset() {
      this.pathIdx = 0;
      this.animating = false;
      this.paused = false;
    },
  }
  animating = ''; // 用于触发重绘

  constructor(rootStore) {
    this.rootStore = rootStore;
    makeAutoObservable(this, { rootStore: false, locationSubscription: false, aniContext: false });
  }

  get api() { return _api; }

  get trace() { return this._trace; }
  async setTrace(v) {
    if (v.id === this._trace?.id) return;
    this.mapContext.reset();
    this.mapContext.path = toJS(v.path);
    this._trace = v;
  }

  getLocations(kind = this.pointKind, keyword = this.pointKeyword) {
    const filter = (p) => {
      if (keyword) {
        const str = [p.metadata.vehicleNo, p.metadata.taskId, p.metadata.driverName].join('|');
        if (str.indexOf(keyword) < 0) return false;
      }
      return true;
    };
    switch (kind) {
      case 1: return this._points.filter((p) => p.metadata.duty).filter(filter);
      case 2: return this._points.filter((p) => !p.metadata.duty).filter(filter);
      default: return this._points.filter(filter);
    }
  }

  vehicleFilter() {
    const keyword = this.pointKeyword;
    const metadata = this.pointKind === 0 ? [] : [{ key: 'duty', op: '=', value: this.pointKind === 1 }];
    const metadataAny = [];
    const kwFilter = { op: 'regex', value: keyword };
    if (keyword) {
      metadataAny.push({ ...kwFilter, key: 'vehicleNo' }, { ...kwFilter, key: 'taskId' }, { ...kwFilter, key: 'driverName' }, { ...kwFilter, key: 'driverId' });
    }
    return {
      metadata,
      metadataAny,
    };
  }

  async fetchPoints() {
    const filter = {}; // this.vehicleFilter();
    const rtn = await _api.locations({ filter });
    this._points = rtn;
  }

  newLocationsArrived(locs) {
    runInAction(() => {
      locs.forEach((loc) => {
        const exists = this._points.find((p) => p.targetId === loc.targetId);
        if (exists) Object.assign(exists, loc); else this._points.push(loc);
      });
    });
  }

  async subscribeLocation({ filter, onNext }) {
    return this.api.subscribeLocation({
      filter,
      onNext,
    });
  }

  traceFilter(keyword) {
    const metadata = [{ key: 'driverState', op: this.traceKind === 0 ? '<' : '>=', value: '5' }];
    const metadataAny = [];
    const kwFilter = { op: 'regex', value: keyword };
    if (keyword) {
      metadataAny.push({ ...kwFilter, key: 'vehicleNo' }, { ...kwFilter, key: 'taskId' });
    }
    return {
      metadata,
      metadataAny,
    };
  }

  async fetchTraces({ pageNo, keyword }) {
    const filter = this.traceFilter(keyword);
    const pg = { page: pageNo, size: PG_SIZE, sort: ['upDate;desc'] };
    const total = await this.api.traceCount({ filter });
    const traces = await this.api.traces({ filter, pg });
    this.traceTotal = total;
    this.traces = pageNo === 0 ? traces : [...this.traces, ...traces];
  }

  async startCarAnimation() {
    const ctx = this.mapContext;
    const line = [...ctx.path];
    ctx.animating = true;
    ctx.paused = false;
    this.animating = 'started';
    while (ctx.pathIdx < line.length - 1) {
      if (!ctx.animating) break;
      await this.startAnimation(line[ctx.pathIdx], line[ctx.pathIdx + 1]);
      ctx.pathIdx++;
    }
    if (ctx.animating) this.stopCarAnimation(); // 非暂停等状态时，动画完成后重置状态
  }

  pauseCarAnimation() {
    const ctx = this.mapContext;
    ctx.animating = false;
    ctx.paused = true;
    this.animating = 'paused';
  }

  stopCarAnimation() {
    const ctx = this.mapContext;
    const { carRef, carLabelRef, path } = ctx;
    if (ctx.pathIdx === 0 && !ctx.animating) return;
    ctx.reset();
    const $this = this;
    setTimeout(() => {
      const loc0 = path[0];
      const point1 = new BMapGL.Point(loc0.lng, loc0.lat);
      carRef.setPosition(point1);
      carRef.setRotation(loc0.bearing);
      carLabelRef.setPosition(point1);
      carLabelRef.setContent(`时速${(loc0.speed * 3.6).toFixed(1)}公里`);
      $this.animating = 'stopped';
    }, 100);
  }

  async locAnimation(line, manualRatio = 20) {
    let idx = 0;
    while (idx < line.length - 1) {
      await this.startAnimation(line[idx], line[idx + 1], manualRatio);
      idx++;
    }
  }

  startAnimation(startPoint, endPoint, manualRatio) {
    const ctx = this.mapContext;
    const { mapRef, carRef, carLabelRef } = this.mapContext;
    const pt1 = new BMapGL.Point(startPoint.lng, startPoint.lat);
    if (!mapRef.getBounds().containsPoint(pt1)) {
      mapRef.setCenter(pt1);
      // mapRef.panTo(pt1);
    }
    return new Promise((resolve) => {
      let line = turf.lineString([[startPoint.lng, startPoint.lat], [endPoint.lng, endPoint.lat]]);
      let distance = turf.distance(turf.point([startPoint.lng, startPoint.lat]), [endPoint.lng, endPoint.lat], { units: 'kilometers' });
      let steps = 1000; // 平滑步长,每公里点数
      let arc = []; // 平滑轨迹
      let done = false;
      for (let i = 0; i < distance; i += distance / steps) { // 插值取点
        let segment = turf.along(line, i, { units: 'kilometers' });
        arc.push(segment.geometry.coordinates);
      }
      let start = null;
      carRef.setRotation(endPoint.bearing);
      let duration = (endPoint.ts - startPoint.ts) * 1000 / (manualRatio || ctx.ratio);
      if (duration > 3000) duration = 3000;
      function animate(timestamp) {
        if (!start) {
          start = timestamp;
        }
        let progress = timestamp - start;
        if (progress < duration && (manualRatio || ctx.animating)) { // 动画长度为1秒
          let idx = Math.floor(progress / duration * arc.length);
          if (idx >= 0 && idx < arc.length) {
            const pt = new BMapGL.Point(arc[idx][0], arc[idx][1]);
            // if (!mapRef.getBounds().containsPoint(pt)) {
            //   mapRef.setCenter(pt);
            // }
            carRef.setPosition(pt);
            carLabelRef.setPosition(pt);
            carLabelRef.setContent(`时速${(endPoint.speed * 3.6).toFixed(1)}公里`);
          }
          if (idx >= arc.length) done = true;
        } else {
          done = true;
        }
        if (!done) {
          window.requestAnimationFrame(animate);
        } else {
          resolve();
        }
      }
      window.requestAnimationFrame(animate);
    });
  }
}


// ==========================================================================
const EARTH_RADIUS = 6371393.0;

function toRadians(degrees) {
  return degrees * (Math.PI / 180.0);
}

/**
 * 将服务器返回的coordinates转换为bm需要的对象
 */
export function aryToLoc(c) {
  try {
    const [lng, lat, alt, ts, speed, bearing] = c;
    return { lng, lat, alt, ts, speed, bearing };
  } catch (e) {
    console.error(e);
    throw e;
  }
}

export function fillLatLng(latLng, coordinate) {
  return { ...aryToLoc(coordinate), ...latLng };
}

/** 处理bm通过GeoJSON转换后的layer中的point，添加speed等  */
export function fillLayerPoints(points, coordinates) {
  points.forEach((p, idx) => { p.latLng = fillLatLng(p.latLng, coordinates[idx]); });
  return points;
}

/**
 * 在location对象中添加coord属性，包含bm绘图需要的坐标等信息
 */
export function fillLocations(locations) {
  const geoJson = {
    type: 'FeatureCollection',
    features: locations.map((loc) => {
      return {
        type: 'Feature',
        geometry: loc.point,
        properties: { loc },
      };
    }),
  };
  return geoParser.readFeaturesFromObject(geoJson, { isPoints: true })
    .map((o) => {
      const { properties: { loc } } = o;
      const bmPt = aryToLoc(loc.point.coordinates);
      loc.coord = { ...bmPt, ...o.points };
      return loc;
    });
}

export function calcDistance(loc1, loc2) {
  if (loc1 == null || loc2 == null) return 0.0;
  let [lng1, lat1] = loc1;
  let [lng2, lat2] = loc2;
  const dLat = toRadians(lat2 - lat1);
  const dLon = toRadians(lng2 - lng1);

  lat1 = toRadians(lat1);
  lat2 = toRadians(lat2);

  const a = Math.pow(Math.sin(dLat / 2), 2) +
    Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

  return EARTH_RADIUS * Math.abs(c);
}

export function calcSpeed(loc1, loc2) {
  if (loc1 == null || loc2 == null) return 0.0;
  const distance = calcDistance(loc1, loc2);
  const dt = loc2[3] - loc1[3];
  if (dt === 0) return 0.0;
  return distance / dt;
}

export function calcDirection(loc1, loc2) {
  if (loc1 == null || loc2 == null) return 0.0;
  let [lng1, lat1] = loc1;
  let [lng2, lat2] = loc2;
  const longitudeDifference = toRadians(lng2 - lng1);

  lat1 = toRadians(lat1);
  lat2 = toRadians(lat2);

  const y = Math.sin(longitudeDifference) * Math.cos(lat2);
  const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(longitudeDifference);

  let bearing = Math.atan2(y, x);

  // Convert radian value to degree, then normalize to [0, 360)
  bearing = (bearing * (180.0 / Math.PI) + 360) % 360;

  return bearing;
}

export function avgSpeed(line) {
  return line.reduce((r, c) => r + c.speed, 0.0) / line.length;
}

export const pointKindText = {
  0: t`全部`,
  1: t`服务中`,
  2: t`空闲`,
};

